#include "d3dUtility.h"

const DWORD d3d::Vertex::FVF = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1;

bool d3d::InitD3D(HWND& hWnd, HINSTANCE hInstance, D3DDEVTYPE deviceType, IDirect3DDevice9** device)
{
	//
	// Create the main application window.
	//

	WNDCLASS wc;

	wc.style         = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc   = (WNDPROC)d3d::WndProc; 
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hInstance;
	wc.hIcon         = LoadIcon(0, IDI_APPLICATION);
	wc.hCursor       = LoadCursor(0, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = 0;
	wc.lpszClassName = WINDOWNAME;

	if( !RegisterClass(&wc) ) 
	{
		::MessageBox(0, "RegisterClass() - FAILED", 0, 0);
		return false;
	}
	
	hWnd = ::CreateWindow(WINDOWNAME, WINDOWNAME, 
		WS_EX_TOPMOST,
		0, 0, WIDTH, HEIGHT,
		0 /*parent hwnd*/, 0 /* menu */, hInstance, 0 /*extra*/); 

	if( !hWnd )
	{
		::MessageBox(0, "CreateWindow() - FAILED", 0, 0);
		return false;
	}

	::ShowWindow(hWnd, SW_SHOW);
	::UpdateWindow(hWnd);

	//
	// Init D3D: 
	//

	HRESULT hr = 0;

	// Step 1: Create the IDirect3D9 object.

	IDirect3D9* d3d9 = 0;
    d3d9 = Direct3DCreate9(D3D_SDK_VERSION);

    if( !d3d9 )
	{
		::MessageBox(0, "Direct3DCreate9() - FAILED", 0, 0);
		return false;
	}

	// Step 2: Check for hardware vp.

	D3DCAPS9 caps;
	d3d9->GetDeviceCaps(D3DADAPTER_DEFAULT, deviceType, &caps);

	int vp = 0;
	if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )
		vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
	else
		vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;

	D3DMULTISAMPLE_TYPE ms = D3DMULTISAMPLE_NONE;
	if( SUCCEEDED( d3d9->CheckDeviceMultiSampleType(D3DADAPTER_DEFAULT, deviceType,
		                                  D3DFMT_A8R8G8B8, WINDOWED,
										  D3DMULTISAMPLE_2_SAMPLES, NULL) )
		 && SUCCEEDED( d3d9->CheckDeviceMultiSampleType(D3DADAPTER_DEFAULT, deviceType,
		                                  D3DFMT_D24S8, WINDOWED,
										  D3DMULTISAMPLE_2_SAMPLES, NULL) ) )
		ms = D3DMULTISAMPLE_2_SAMPLES;


	// Step 3: Fill out the D3DPRESENT_PARAMETERS structure.
 
	D3DPRESENT_PARAMETERS d3dpp;
	d3dpp.BackBufferWidth            = WIDTH;
	d3dpp.BackBufferHeight           = HEIGHT;
	d3dpp.BackBufferFormat           = D3DFMT_A8R8G8B8;
	d3dpp.BackBufferCount            = 1;
	d3dpp.MultiSampleType            = D3DMULTISAMPLE_NONE;
	d3dpp.MultiSampleQuality         = 0;
	d3dpp.SwapEffect                 = D3DSWAPEFFECT_DISCARD; 
	d3dpp.hDeviceWindow              = hWnd;
	d3dpp.Windowed                   = WINDOWED;
	d3dpp.EnableAutoDepthStencil     = true; 
	d3dpp.AutoDepthStencilFormat     = D3DFMT_D24S8;
	d3dpp.MultiSampleType            = ms;
	d3dpp.Flags                      = 0;
	d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
	d3dpp.PresentationInterval       = D3DPRESENT_INTERVAL_IMMEDIATE;

	// Step 4: Create the device.

	hr = d3d9->CreateDevice(
		D3DADAPTER_DEFAULT, // primary adapter
		deviceType,         // device type
		hWnd,               // window associated with device
		vp,                 // vertex processing
	    &d3dpp,             // present parameters
	    device);            // return created device

	if( FAILED(hr) )
	{
		// try again using a 16-bit depth buffer
		d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
		
		hr = d3d9->CreateDevice(
			D3DADAPTER_DEFAULT,
			deviceType,
			hWnd,
			vp,
			&d3dpp,
			device);

		if( FAILED(hr) )
		{
			d3d9->Release(); // done with d3d9 object
			::MessageBox(0, "CreateDevice() - FAILED", 0, 0);
			return false;
		}
	}

	d3d9->Release(); // done with d3d9 object
	
	return true;
}

bool d3d::InitD3DInput(HWND hWnd, IDirectInputDevice8** ppDIDevKeyboard, IDirectInputDevice8** ppDIDevMouse)
{
	HRESULT hr;
	LPDIRECTINPUT8 pDI;

	hr = DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&pDI, NULL);
	if( FAILED(hr) ) return false;
	
	if( ppDIDevKeyboard != NULL || *ppDIDevKeyboard != NULL)
	{
		hr = pDI->CreateDevice(GUID_SysKeyboard, ppDIDevKeyboard, NULL);
		if( FAILED(hr) )
		{
			pDI->Release();
			return false;
		}

		hr = (*ppDIDevKeyboard)->SetDataFormat(&c_dfDIKeyboard);
		if( FAILED(hr) )
		{
			(*ppDIDevKeyboard)->Release();
			pDI->Release();
			return false;
		}

		hr = (*ppDIDevKeyboard)->SetCooperativeLevel(hWnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
		if( FAILED(hr) )
		{
			(*ppDIDevKeyboard)->Release();
			pDI->Release();
			return false;
		}

		hr = (*ppDIDevKeyboard)->Acquire();
		if( FAILED(hr) )
		{
			(*ppDIDevKeyboard)->Release();
			pDI->Release();
			return false;
		}

		(*ppDIDevKeyboard)->Acquire();
	}

	if( ppDIDevMouse != NULL || *ppDIDevMouse != NULL )
	{
		hr = pDI->CreateDevice(GUID_SysMouse, ppDIDevMouse, NULL);
		if( FAILED(hr) )
		{
			pDI->Release();
			return false;
		}

		hr = (*ppDIDevMouse)->SetDataFormat(&c_dfDIMouse2);
		if( FAILED(hr) )
		{
			(*ppDIDevMouse)->Release();
			pDI->Release();
			return false;
		}

		hr = (*ppDIDevMouse)->SetCooperativeLevel(hWnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
		if( FAILED(hr) )
		{
			(*ppDIDevMouse)->Release();
			pDI->Release();
			return false;
		}

		hr = (*ppDIDevMouse)->Acquire();
		if( FAILED(hr) )
		{
			(*ppDIDevMouse)->Release();
			pDI->Release();
			return false;
		}
	}

	pDI->Release();	
	return true;
}

bool d3d::ReadKeyboardState(IDirectInputDevice8* pDIDevKeyboard, BYTE pbyKSNow[256])
{
	static BYTE abyKSLast[256];

    if( pDIDevKeyboard == NULL || pbyKSNow == NULL ) 
        return false;
    
    ZeroMemory( pbyKSNow, sizeof(BYTE) * 256 );
	HRESULT hr = pDIDevKeyboard->GetDeviceState( sizeof(BYTE) * 256, pbyKSNow );

	/**/
    if( FAILED(hr) ) 
    {
        hr = pDIDevKeyboard->Acquire();
        while( hr == DIERR_INPUTLOST ) 
            hr = pDIDevKeyboard->Acquire();

        return false;
    }
	/**/

	int i;

	BYTE abyKSTemp[256];
	for( i = 0; i < 256; i++ )
		abyKSTemp[i] = abyKSLast[i];

	for( i = 0; i < 256; i++ )
	{
		abyKSLast[i] = pbyKSNow[i];
		if( abyKSTemp[i] & 0x80 )
			pbyKSNow[i] = 0x40;
	}

    return true;
}

bool d3d::ReadMouseState(IDirectInputDevice8* pDIDevMouse, DIMOUSESTATE2* pdims2Now)
{
    static DIMOUSESTATE2 dims2Last;      // DirectInput mouse state structure

	if( pDIDevMouse == NULL || pdims2Now == NULL ) 
        return false;
    
    ZeroMemory( pdims2Now, sizeof(DIMOUSESTATE2) );
    HRESULT hr = pDIDevMouse->GetDeviceState( sizeof(DIMOUSESTATE2), pdims2Now );

	/**/
    if( FAILED(hr) ) 
    {
        hr = pDIDevMouse->Acquire();
        while( hr == DIERR_INPUTLOST ) 
            hr = pDIDevMouse->Acquire();

		return false;
    }
	/**/

	DIMOUSESTATE2 dims2Temp;
	dims2Temp = dims2Last;

	dims2Last = *pdims2Now;
	for( int i = 0; i < 8; i++ )
	{
		if( dims2Temp.rgbButtons[i] & 0x80 )
			pdims2Now->rgbButtons[i] = 0x40;
	}

	return true;
}

bool d3d::CheckPressAnyKeybyKeyboard(BYTE* abyKS, PRESSINGSTATE ps)
{
	for( int i = 0; i < 256; i++ )
	{
		if( abyKS[i] & ps )
			return true;
	}

	return false;
}

bool d3d::CheckPressAnyKeybyMouse(DIMOUSESTATE2* pdims2, PRESSINGSTATE ps)
{
	for( int i = 0; i < 8; i++ )
	{
		if( pdims2->rgbButtons[i] & ps )
			return true;
	}

	return false;
}

int d3d::EnterMsgLoop( bool (*ptr_display)(float timeDelta) )
{
	MSG msg;
	::ZeroMemory(&msg, sizeof(MSG));

	static float lastTime = (float)timeGetTime(); 

	while(msg.message != WM_QUIT)
	{
		if(::PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			::TranslateMessage(&msg);
			::DispatchMessage(&msg);
		}
		else
        {	
			float currTime  = (float)timeGetTime();
			float timeDelta = (currTime - lastTime)*0.001f;

			ptr_display(timeDelta);

			lastTime = currTime;
        }
    }
    return msg.wParam;
}

D3DLIGHT9 d3d::InitDirectionalLight(D3DXVECTOR3* direction, D3DXCOLOR* color)
{
	D3DLIGHT9 light;
	::ZeroMemory(&light, sizeof(light));

	light.Type      = D3DLIGHT_DIRECTIONAL;
	light.Ambient   = *color * 0.4f;
	light.Diffuse   = *color;
	light.Specular  = *color * 0.6f;
	light.Direction = *direction;

	return light;
}

D3DLIGHT9 d3d::InitPointLight(D3DXVECTOR3* position, D3DXCOLOR* color)
{
	D3DLIGHT9 light;
	::ZeroMemory(&light, sizeof(light));

	light.Type      = D3DLIGHT_POINT;
	light.Ambient   = *color * 0.4f;
	light.Diffuse   = *color;
	light.Specular  = *color * 0.6f;
	light.Position  = *position;
	light.Range        = 1000.0f;
	light.Falloff      = 1.0f;
	light.Attenuation0 = 1.0f;
	light.Attenuation1 = 0.0f;
	light.Attenuation2 = 0.0f;

	return light;
}

D3DLIGHT9 d3d::InitSpotLight(D3DXVECTOR3* position, D3DXVECTOR3* direction, D3DXCOLOR* color)
{
	D3DLIGHT9 light;
	::ZeroMemory(&light, sizeof(light));

	light.Type      = D3DLIGHT_SPOT;
	light.Ambient   = *color * 0.5f;
	light.Diffuse   = *color;
	light.Specular  = *color * 0.6f;
	light.Position  = *position;
	light.Direction = *direction;
	light.Range        = 1000.0f;
	light.Falloff      = 1.0f;
	light.Attenuation0 = 1.0f;
	light.Attenuation1 = 0.0f;
	light.Attenuation2 = 0.0f;
	light.Theta        = 0.0f;
	light.Phi          = 0.0f;

	return light;
}

D3DMATERIAL9 d3d::InitMtrl(D3DXCOLOR ambient, D3DXCOLOR diffuse, D3DXCOLOR specular, D3DXCOLOR emissive, float power)
{
	D3DMATERIAL9 mtrl;
	mtrl.Ambient  = ambient;
	mtrl.Diffuse  = diffuse;
	mtrl.Specular = specular;
	mtrl.Emissive = emissive;
	mtrl.Power    = power;
	return mtrl;
}

d3d::BoundingBox::BoundingBox()
{
	// infinite small 
	_min.x = d3d::INFINITY;
	_min.y = d3d::INFINITY;
	_min.z = d3d::INFINITY;

	_max.x = -d3d::INFINITY;
	_max.y = -d3d::INFINITY;
	_max.z = -d3d::INFINITY;
}

bool d3d::BoundingBox::isPointInside(D3DXVECTOR3& p)
{
	if( p.x >= _min.x && p.y >= _min.y && p.z >= _min.z &&
		p.x <= _max.x && p.y <= _max.y && p.z <= _max.z )
	{
		return true;
	}
	else
	{
		return false;
	}
}

d3d::BoundingSphere::BoundingSphere()
{
	_radius = 0.0f;
}

d3d::BoundingCircle::BoundingCircle()
{
	_radius = 0.0f;
}

void d3d::GetCursorPosInScreen(POINT* pt)
{
	GetCursorPos(pt);

	HWND hWnd = GetFocus();
	RECT rt;
	GetWindowRect(hWnd, &rt);

	pt->x -= rt.left;
	pt->y -= rt.top;
	pt->y -= 5;
}

void d3d::CalcPickingRay(int x, int y, D3DVIEWPORT9* vp, D3DXMATRIX* matProj, D3DXMATRIX* matViewInverse, d3d::Ray* pickingRay)
{
	float px = ((( 2.0f * x) / vp->Width)  - 1.0f) / (*matProj)(0, 0);
	float py = (((-2.0f * y) / vp->Height) + 1.0f) / (*matProj)(1, 1);
	
	pickingRay->_origin    = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
	pickingRay->_direction = D3DXVECTOR3(  px,   py, 1.0f);

	D3DXVec3TransformCoord( &pickingRay->_origin,    &pickingRay->_origin,    matViewInverse);
	D3DXVec3TransformNormal(&pickingRay->_direction, &pickingRay->_direction, matViewInverse);
	D3DXVec3Normalize(&pickingRay->_direction, &pickingRay->_direction);
}

bool d3d::raySphereIntersectionTest(d3d::Ray* pickingRay, BoundingSphere* sphere)
{
	D3DXVECTOR3 v = pickingRay->_origin - sphere->_center;

	float b = 2.0f * D3DXVec3Dot(&pickingRay->_direction, &v);
	float c = D3DXVec3Dot(&v, &v) - (sphere->_radius * sphere->_radius);

	float discriminant = (b * b) - (4.0f * c);

	if( discriminant < 0.0f )
		return false;

	discriminant = sqrtf(discriminant);

	float s0 = (-b + discriminant) / 2.0f;
	float s1 = (-b - discriminant) / 2.0f;

	if( s0 >= 0.0f || s1 >= 0.0f )
		return true;

	return false;
}

float d3d::rayCircleIntersectionTest(d3d::Ray* pickingRay, BoundingCircle* circle)
{
	D3DXVECTOR3 v;
	D3DXVECTOR3 start(pickingRay->_origin);
	D3DXVECTOR3 end(pickingRay->_origin + pickingRay->_direction * 100.0f);
	D3DXPlaneIntersectLine(&v, &circle->_plane, &start, &end );

	float length = fabsf(D3DXVec3Length(&(v - circle->_center)));

	if( length <= circle->_radius )
		return D3DXVec3Length(&(v - pickingRay->_origin));

	return false;
}

int d3d::GetRandomInt(int lowBound, int highBound)
{
	if( lowBound >= highBound ) // bad input
		return lowBound;

	return (rand() % (highBound - lowBound)) + lowBound;
}

float d3d::GetRandomFloat(float lowBound, float highBound)
{
	if( lowBound >= highBound ) // bad input
		return lowBound;

	float f = (rand() % 10000) * 0.0001f; 
	return (f * (highBound - lowBound)) + lowBound; 
}

void d3d::GetRandomVector(D3DXVECTOR3* out, D3DXVECTOR3* min, D3DXVECTOR3* max)
{
	out->x = GetRandomFloat(min->x, max->x);
	out->y = GetRandomFloat(min->y, max->y);
	out->z = GetRandomFloat(min->z, max->z);
}

int d3d::PressKey(char ch, bool& bContinue) // made by Ricale Verst.. kkkkkkfuhahahah
{
	if(::GetAsyncKeyState(ch) & 0x8000f && !bContinue )
	{
		bContinue = true;
		return true;
	}
	else if( !(::GetAsyncKeyState(ch) & 0x8000f) && bContinue )
		bContinue = false;

	return false;

	return 0;
}

DWORD d3d::FtoDw(float f)
{
	return *((DWORD*)&f);
}

float d3d::Lerp(float a, float b, float t)
{
	return a - (a*t) + (b*t);
}

void d3d::ComputeNormal(D3DXVECTOR3* pOut, D3DXVECTOR3* p0, D3DXVECTOR3* p1, D3DXVECTOR3* p2)
{
	D3DXVECTOR3 t = *p1 - *p0;
	D3DXVECTOR3 u = *p2 - *p0;

	D3DXVec3Cross(pOut, &t, &u);
	D3DXVec3Normalize(pOut, pOut);
}

